package com.wisdom.collect.modbus.facade;

import com.wisdom.collect.modbus.Modbus;
import com.wisdom.collect.modbus.ModbusException;
import com.wisdom.collect.modbus.io.AbstractModbusTransport;
import com.wisdom.collect.modbus.io.ModbusTransaction;
import com.wisdom.collect.modbus.msg.ModbusResponse;
import com.wisdom.collect.modbus.msg.ReadCoilsRequest;
import com.wisdom.collect.modbus.msg.ReadCoilsResponse;
import com.wisdom.collect.modbus.msg.ReadInputDiscretesRequest;
import com.wisdom.collect.modbus.msg.ReadInputDiscretesResponse;
import com.wisdom.collect.modbus.msg.ReadInputRegistersRequest;
import com.wisdom.collect.modbus.msg.ReadInputRegistersResponse;
import com.wisdom.collect.modbus.msg.ReadMultipleRegistersRequest;
import com.wisdom.collect.modbus.msg.ReadMultipleRegistersResponse;
import com.wisdom.collect.modbus.msg.WriteCoilRequest;
import com.wisdom.collect.modbus.msg.WriteCoilResponse;
import com.wisdom.collect.modbus.msg.WriteMultipleCoilsRequest;
import com.wisdom.collect.modbus.msg.WriteMultipleRegistersRequest;
import com.wisdom.collect.modbus.msg.WriteSingleRegisterRequest;
import com.wisdom.collect.modbus.procimg.InputRegister;
import com.wisdom.collect.modbus.procimg.Register;
import com.wisdom.collect.modbus.util.BitVector;

abstract public class AbstractModbusMaster {

    private static final int DEFAULT_UNIT_ID = 1;

    protected ModbusTransaction transaction;
    private ReadCoilsRequest readCoilsRequest;
    private ReadInputDiscretesRequest readInputDiscretesRequest;
    private WriteCoilRequest writeCoilRequest;
    private WriteMultipleCoilsRequest writeMultipleCoilsRequest;
    private ReadInputRegistersRequest readInputRegistersRequest;
    private ReadMultipleRegistersRequest readMultipleRegistersRequest;
    private WriteSingleRegisterRequest writeSingleRegisterRequest;
    private WriteMultipleRegistersRequest writeMultipleRegistersRequest;
    protected int timeout = Modbus.DEFAULT_TIMEOUT;


    protected synchronized void setTransaction(ModbusTransaction transaction) {
        this.transaction = transaction;
    }

    abstract public void connect() throws Exception;

    abstract public void disconnect();

    public BitVector readCoils(int unitId, int ref, int count) throws ModbusException {
        checkTransaction();
        if (readCoilsRequest == null) {
            readCoilsRequest = new ReadCoilsRequest();
        }
        readCoilsRequest.setUnitID(unitId);
        readCoilsRequest.setReference(ref);
        readCoilsRequest.setBitCount(count);
        transaction.setRequest(readCoilsRequest);
        transaction.execute();
        BitVector bv = ((ReadCoilsResponse) getAndCheckResponse()).getCoils();
        bv.forceSize(count);
        return bv;
    }

    public boolean writeCoil(int unitId, int ref, boolean state) throws ModbusException {
        checkTransaction();
        if (writeCoilRequest == null) {
            writeCoilRequest = new WriteCoilRequest();
        }
        writeCoilRequest.setUnitID(unitId);
        writeCoilRequest.setReference(ref);
        writeCoilRequest.setCoil(state);
        transaction.setRequest(writeCoilRequest);
        transaction.execute();
        return ((WriteCoilResponse) getAndCheckResponse()).getCoil();
    }

    public void writeMultipleCoils(int unitId, int ref, BitVector coils) throws ModbusException {
        checkTransaction();
        if (writeMultipleCoilsRequest == null) {
            writeMultipleCoilsRequest = new WriteMultipleCoilsRequest();
        }
        writeMultipleCoilsRequest.setUnitID(unitId);
        writeMultipleCoilsRequest.setReference(ref);
        writeMultipleCoilsRequest.setCoils(coils);
        transaction.setRequest(writeMultipleCoilsRequest);
        transaction.execute();
    }

    public BitVector readInputDiscretes(int unitId, int ref, int count) throws ModbusException {
        checkTransaction();
        if (readInputDiscretesRequest == null) {
            readInputDiscretesRequest = new ReadInputDiscretesRequest();
        }
        readInputDiscretesRequest.setUnitID(unitId);
        readInputDiscretesRequest.setReference(ref);
        readInputDiscretesRequest.setBitCount(count);
        transaction.setRequest(readInputDiscretesRequest);
        transaction.execute();
        BitVector bv = ((ReadInputDiscretesResponse)getAndCheckResponse()).getDiscretes();
        bv.forceSize(count);
        return bv;
    }

    public InputRegister[] readInputRegisters(int unitId, int ref, int count) throws ModbusException {
        checkTransaction();
        if (readInputRegistersRequest == null) {
            readInputRegistersRequest = new ReadInputRegistersRequest();
        }
        readInputRegistersRequest.setUnitID(unitId);
        readInputRegistersRequest.setReference(ref);
        readInputRegistersRequest.setWordCount(count);
        transaction.setRequest(readInputRegistersRequest);
        transaction.execute();
        return ((ReadInputRegistersResponse) getAndCheckResponse()).getRegisters();
    }

    public Register[] readMultipleRegisters(int unitId, int ref, int count) throws ModbusException {
        checkTransaction();
        if (readMultipleRegistersRequest == null) {
            readMultipleRegistersRequest = new ReadMultipleRegistersRequest();
        }
        readMultipleRegistersRequest.setUnitID(unitId);
        readMultipleRegistersRequest.setReference(ref);
        readMultipleRegistersRequest.setWordCount(count);
        transaction.setRequest(readMultipleRegistersRequest);
        transaction.execute();
        return ((ReadMultipleRegistersResponse) getAndCheckResponse()).getRegisters();
    }

    public void writeSingleRegister(int unitId, int ref, Register register) throws ModbusException {
        checkTransaction();
        if (writeSingleRegisterRequest == null) {
            writeSingleRegisterRequest = new WriteSingleRegisterRequest();
        }
        writeSingleRegisterRequest.setUnitID(unitId);
        writeSingleRegisterRequest.setReference(ref);
        writeSingleRegisterRequest.setRegister(register);
        transaction.setRequest(writeSingleRegisterRequest);
        transaction.execute();
    }

    public void writeMultipleRegisters(int unitId, int ref, Register[] registers) throws ModbusException {
        checkTransaction();
        if (writeMultipleRegistersRequest == null) {
            writeMultipleRegistersRequest = new WriteMultipleRegistersRequest();
        }
        writeMultipleRegistersRequest.setUnitID(unitId);
        writeMultipleRegistersRequest.setReference(ref);
        writeMultipleRegistersRequest.setRegisters(registers);
        transaction.setRequest(writeMultipleRegistersRequest);
        transaction.execute();
    }

    public BitVector readCoils(int ref, int count) throws ModbusException {
        return readCoils(DEFAULT_UNIT_ID, ref, count);
    }

    public boolean writeCoil(int ref, boolean state) throws ModbusException {
        return writeCoil(DEFAULT_UNIT_ID, ref, state);
    }

    public void writeMultipleCoils(int ref, BitVector coils) throws ModbusException {
        writeMultipleCoils(DEFAULT_UNIT_ID, ref, coils);
    }


    public BitVector readInputDiscretes(int ref, int count) throws ModbusException {
        return readInputDiscretes(DEFAULT_UNIT_ID, ref, count);
    }

    public InputRegister[] readInputRegisters(int ref, int count) throws ModbusException {
        return readInputRegisters(DEFAULT_UNIT_ID, ref, count);
    }

    public Register[] readMultipleRegisters(int ref, int count) throws ModbusException {
        return readMultipleRegisters(DEFAULT_UNIT_ID, ref, count);
    }

    public void writeSingleRegister(int ref, Register register) throws ModbusException {
        writeSingleRegister(DEFAULT_UNIT_ID, ref, register);
    }

    public void writeMultipleRegisters(int ref, Register[] registers) throws ModbusException {
        writeMultipleRegisters(DEFAULT_UNIT_ID, ref, registers);
    }

    private ModbusResponse getAndCheckResponse() throws ModbusException {
        ModbusResponse res = transaction.getResponse();
        if (res == null) {
            throw new ModbusException("No response");
        }
        return res;
    }

    private void checkTransaction() throws ModbusException {
        if (transaction == null) {
            throw new ModbusException("No transaction created, probably not connected");
        }
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    synchronized public void setRetries(int retries) {
        if (transaction != null) {
            transaction.setRetries(retries);
        }
    }

    synchronized public void setCheckingValidity(boolean b) {
        if (transaction != null) {
            transaction.setCheckingValidity(b);
        }
    }

    public abstract AbstractModbusTransport getTransport();

}